package com.jctrip.hotel.source.service.elong.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jctrip.hotel.common.constant.RedisKeyConstant;
import com.jctrip.hotel.common.constant.XxlJobNameConstant;
import com.jctrip.hotel.common.enums.SourceEnum;
import com.jctrip.hotel.common.threadpool.XxlJobThreadPool;
import com.jctrip.hotel.common.util.DateUtil;
import com.jctrip.hotel.common.util.JsonUtil;
import com.jctrip.hotel.common.util.RedisUtil;
import com.jctrip.hotel.common.util.StringUtil;
import com.jctrip.hotel.domain.entity.*;
import com.jctrip.hotel.source.client.HworldCallBackClient;
import com.jctrip.hotel.source.model.elong.ElongLocalEnum;
import com.jctrip.hotel.source.model.elong.OfflineFliggyRp;
import com.jctrip.hotel.source.model.elong.req.offline.*;
import com.jctrip.hotel.source.model.elong.req.online.ElongHotelDetailReq;
import com.jctrip.hotel.source.model.elong.resp.offline.*;
import com.jctrip.hotel.source.model.elong.resp.online.ElongHotelDetailResp;
import com.jctrip.hotel.source.service.basic.*;
import com.jctrip.hotel.source.service.elong.ElongService;
import com.jctrip.hotel.source.service.elong.ElongTaskService;
import com.jctrip.hotel.source.xxl.elong.thread.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @Author: zxy
 * @CreateTime: 2023-08-21 17:05
 * @Description:
 */
@Service
@Slf4j
public class ElongTaskServiceImpl implements ElongTaskService {
    private final static Integer QPS_COUNT = 3;
    private final static Long TIME_OUT = 1500L;
    private static final long PUSH_EXPIRY = 1 * 60;
    private static final XxlJobThreadPool hotelIdListTaskPool =
            new XxlJobThreadPool(XxlJobNameConstant.SYNC_ELONG_HOTEL_POP);
    private static final XxlJobThreadPool hotelRatesTaskPool =
            new XxlJobThreadPool(XxlJobNameConstant.SYNC_ELONG_HOTEL_RATES_POP);
    private static final XxlJobThreadPool hotelChoseRatesTaskPool =
            new XxlJobThreadPool(XxlJobNameConstant.SYNC_ELONG_HOTEL_CHOSE_RATES_POP);
    @Resource
    RedisUtil redisUtil;
    @Autowired
    private ElongService elongService;
    @Resource
    private HtHotelStdInfoService htHotelStdInfoService;
    @Resource
    private HtRoomTypeStdInfoService htRoomTypeStdInfoService;
    @Resource
    private HtPriceCalendarElongService htPriceCalendarElongService;
    @Autowired
    private HtPriceCalendarElongProductService htPriceCalendarElongProductService;
    @Autowired
    private HworldCallBackClient hworldCallBackClient;
    @Autowired
    private HtPricePolicyService htPricePolicyService;

    @Override
    public void syncHotelIdListTaskFastPush(String user, String appKey, String secretKey, String local, ElongQueryCityReq req) {
        try {
            if (redisUtil.setENx(RedisKeyConstant.SYNC_MAIN_ELONG_HOTELId_PUSH, PUSH_EXPIRY,
                    RedisKeyConstant.SYNC_MAIN_ELONG_HOTELId_PUSH)) {
                Integer pageIndex = req.getPageIndex();
                List<String> hotelIds = new ArrayList<>();
                while (true) {
                    ElongQueryCityResp elongQueryCityResp = elongService.queryElongCity(user, appKey, secretKey, local, req);
                    if (null == elongQueryCityResp || CollectionUtils.isEmpty(elongQueryCityResp.getCitys())) {
                        break;
                    }
                    hotelIds.addAll(elongQueryCityResp.getCitys()
                            .stream().map(ElongQueryCityResp.City::getCityId).collect(Collectors.toList()));
                    req.setPageIndex(++pageIndex);
                }
                if (CollectionUtil.isNotEmpty(hotelIds)
                        && redisUtil.lGetListSize(RedisKeyConstant.SYNC_MAIN_ELONG_HOTELId_LIST) <= 0) {
                    redisUtil.leftPushAll(RedisKeyConstant.SYNC_MAIN_ELONG_HOTELId_LIST,
                            new ArrayList<>(hotelIds));
                }

            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void syncHotelInfoListTaskFastPop(String user, String appKey, String secretKey, String local) {
        hotelIdListTaskPool.execute(() -> new SyncElongHotelInfoThread(user, appKey, secretKey, local));
    }

    @Override
    public void syncHotelInfoTask(String user, String appKey, String secretKey, String local,
                                  ElongQueryHotelListReq elongQueryHotelListReq) {
        try {
            Integer pageIndex = elongQueryHotelListReq.getPageIndex();
            // 查询出所有城市的酒店
            while (true) {
                ElongQueryHotelListResp elongQueryHotelListResp =
                        elongService.queryElongHotelList(user, appKey, secretKey, local, elongQueryHotelListReq);
                if (elongQueryHotelListResp == null || CollectionUtils.isEmpty(elongQueryHotelListResp.getHotels())) {
                    break;
                }
                elongQueryHotelListResp.getHotels().forEach(hotelCity -> {
                    ElongQueryHotelReq elongQueryHotelReq = ElongQueryHotelReq.builder()
                            .hotelId(hotelCity.getHotelId()).build();
                    ElongQueryHotelResp elongQueryHotelResp =
                            elongService.queryElongHotel(user, appKey, secretKey, local, elongQueryHotelReq);
                    HtHotelStdInfo info = parseHotelInfo(elongQueryHotelResp.getDetail());
                    info.setExtra(JsonUtil.toString(elongQueryHotelResp.getSuppliers()));

                    htHotelStdInfoService.saveOrUpdate(info,
                            new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getHotelId, info.getHotelId())
                                    .eq(HtHotelStdInfo::getSourceCode, SourceEnum.ELONGDOM.getCode()));
                    try {
                        Thread.sleep(300);
                    } catch (Exception e) {

                    }
                    // 房型
                    if (CollectionUtils.isEmpty(elongQueryHotelResp.getRooms())) {
                        return;
                    }
                    elongQueryHotelResp.getRooms().forEach(roomType -> {
                        HtRoomTypeStdInfo roomTypeStdInfo = new HtRoomTypeStdInfo();
                        roomTypeStdInfo.setHotelId(info.getHotelId());
                        roomTypeStdInfo.setRoomTypeCode(roomType.getRoomID());
                        roomTypeStdInfo.setRoomTypeName(roomType.getRoomName());
                        roomTypeStdInfo.setMaxCheckIn(roomType.getCapacity());
                        roomTypeStdInfo.setFloor(roomType.getFloor());

                        Integer bedType = null;
                        Integer addBed = null;
                        Integer windowType = null;
                        if (!CollectionUtils.isEmpty(roomType.getFacilities())) {
                            for (ElongQueryHotelResp.Facility facility : roomType.getFacilities()) {
                                addBed = addBed == null ? getAddBed(facility.getFacilityName()) : addBed;
                                windowType = windowType == null ? getWindowType(facility.getFacilityName()) : windowType;
                                bedType = bedType == null ? getBedType(facility.getFacilityName()) : bedType;
                            }
                        }
                        roomTypeStdInfo.setBedType(bedType);
                        roomTypeStdInfo.setWindowType(windowType);
                        roomTypeStdInfo.setAddBed(addBed);
                        htRoomTypeStdInfoService.saveOrUpdate(roomTypeStdInfo,
                                new LambdaQueryWrapper<HtRoomTypeStdInfo>().eq(HtRoomTypeStdInfo::getHotelId,
                                        roomTypeStdInfo.getHotelId())
                                        .eq(HtRoomTypeStdInfo::getRoomTypeCode, roomTypeStdInfo.getRoomTypeCode()));
                    });
                });

                elongQueryHotelListReq.setPageIndex(++pageIndex);
            }


        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void syncHotelRatesPush(String user, String appKey, String secretKey, String local) {
        try {
            List<HtHotelStdInfo> htHotelStdInfos = htHotelStdInfoService.list(
                    new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getSourceCode,
                            SourceEnum.ELONGDOM.getCode()).eq(HtHotelStdInfo::getSourceStatus, 1)
                            .eq(HtHotelStdInfo::getBookStatus, 1));
            if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isNotEmpty(htHotelStdInfos)
                    && redisUtil.lGetListSize(RedisKeyConstant.SYNC_MAIN_ELONG_HOTEL_RATE_PUSH) <= 0) {
                redisUtil.leftPushAll(RedisKeyConstant.SYNC_MAIN_ELONG_HOTEL_RATE_PUSH,
                        htHotelStdInfos.stream().map(HtHotelStdInfo::getHotelId).collect(Collectors.toList()));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void syncHotelRatesPop(String sourceId, String user, String appKey, String secretKey, String local, Integer days) {
        hotelRatesTaskPool.execute(
                () -> new SyncElongHotelRatesThread(sourceId, user, appKey, secretKey, local, days));
    }

    @Override
    public void syncHotelRates(String sourceId, String user, String appKey, String secretKey, String local,
                               String hotel, Date startDate, Date endDate, Integer days) {
        // 防止有rp没有被下线了 先update
        htPriceCalendarElongService.update(null, new LambdaUpdateWrapper<HtPriceCalendarElong>()
                .set(HtPriceCalendarElong::getQuota, 0)
                .eq(HtPriceCalendarElong::getHotelId, hotel));
        // 价格
        ElongQueryRateReq rateReq = ElongQueryRateReq.builder()
                .hotelIds(elongService.parseHotelId(hotel)).startDate(startDate).endDate(endDate)
//                .invoiceMode(ElongLocalEnum.ELONG.getCode())
                // 开票模式修改为全部
                .invoiceMode(ElongLocalEnum.ALL.getCode())
                .paymentType(ElongLocalEnum.PREPAY.getCode()).build();
        ELongQueryRateResp rateResp = elongService.queryElongRate(user, appKey, secretKey, local, rateReq);

        // 库存
        ElongQueryInventoryReq inventoryReq = ElongQueryInventoryReq.builder()
                .hotelIds(elongService.parseHotelId(hotel)).startDate(startDate).endDate(endDate).build();
        ElongQueryInventoryResp inventoryResp = elongService.queryElongInventory(user, appKey, secretKey, local, inventoryReq);

        // 产品 不需要钟点房
        ElongQueryHotelRpReq rpReq = ElongQueryHotelRpReq.builder()
                .hotelIds(elongService.parseHotelId(hotel)).options("1")
                // 开票模式修改为全部
                .invoiceMode(ElongLocalEnum.ALL.getCode()).paymentType(ElongLocalEnum.PREPAY.getCode()).build();
        List<ElongQueryHotelRpResp.Hotel> hotels = elongService.queryElongHotelRp(user, appKey, secretKey, local, rpReq).getHotels();

        if (Objects.isNull(rateResp) || Objects.isNull(inventoryResp)
                || CollectionUtils.isEmpty(rateResp.getRates())
                || CollectionUtils.isEmpty(inventoryResp.getInventories())) {
            return;
        }

        // 只会查询一个酒店 默认第一个
        ElongQueryHotelRpResp.Hotel Hotel = hotels.get(0);
        if (Objects.isNull(Hotel)) {
            return;
        }

        HtHotelStdInfo htHotelStdInfo = htHotelStdInfoService.getOne(
                new LambdaQueryWrapper<HtHotelStdInfo>()
                        .select(HtHotelStdInfo::getExtra)
                        .eq(HtHotelStdInfo::getHotelId, hotel));

        inventoryResp.getInventories().forEach(inventory -> {
            // 找到库存对应的价格
            ELongQueryRateResp.Rate rate = rateResp.getRates().stream()
                    .filter(e -> inventory.getHotelId().equals(e.getHotelId())
                            && inventory.getHotelCode().equals(e.getHotelCode())
                            && inventory.getRoomTypeId().equals(e.getRoomTypeId())
                            && inventory.getDate().getTime() <= e.getEndDate().getTime()
                            && inventory.getDate().getTime() >= e.getStartDate().getTime())
                    .findFirst().orElse(null);

            if (Objects.isNull(rate)) {
                return;
            }

            HtPriceCalendarElong htPriceCalendarElong = new HtPriceCalendarElong();
            htPriceCalendarElong.setSourceId(sourceId);
            htPriceCalendarElong.setHotelId(elongService.parseInnId(rate.getHotelId()));
            // roomType 需要去rp的suppliers里找到对应的roomId
            ElongQueryHotelRpResp.Supplier supplier = Hotel.getSuppliers().stream()
                    .filter(e -> e.getHotelCode().equals(rate.getHotelCode())).findFirst().orElse(null);
            if (Objects.isNull(supplier) || StringUtils.isBlank(supplier.getSupplierType())) {
                return;
            }
            ElongQueryHotelRpResp.Room room = supplier.getRooms().stream()
                    .filter(e -> e.getRoomTypeId().equals(rate.getRoomTypeId())).findFirst().orElse(null);
            if (Objects.isNull(room)) {
                return;
            }
            htPriceCalendarElong.setRoomTypeCode(room.getRoomId());
            htPriceCalendarElong.setRoomTypeName(Optional.ofNullable(htRoomTypeStdInfoService.getOne(
                    new LambdaQueryWrapper<HtRoomTypeStdInfo>()
                            .eq(HtRoomTypeStdInfo::getHotelId, htPriceCalendarElong.getHotelId())
                            .eq(HtRoomTypeStdInfo::getRoomTypeCode, room.getRoomId())))
                    .orElse(new HtRoomTypeStdInfo()).getRoomTypeName());
            if (StringUtils.isBlank(htPriceCalendarElong.getRoomTypeName())) {
                return;
            }
            htPriceCalendarElong.setProductId(String.format("%s-%s-%s-%s", rate.getRoomTypeId(), rate.getRatePlanId(), rate.getHotelCode(), supplier.getInvoiceMode()));
            htPriceCalendarElong.setCalDate(inventory.getDate());
            htPriceCalendarElong.setQuota(inventory.getStatus() && inventory.getOverBooking() != null ?
                    inventory.getOverBooking() == 0 ? 3 : inventory.getAmount() : 0);
            htPriceCalendarElong.setCurrencyCode(rate.getCurrencyCode());
            htPriceCalendarElong.setOverBooking(1 - inventory.getOverBooking());
            // 获取对应hotelCode的rateplan
            ElongQueryHotelRpResp.RatePlan ratePlan = Hotel.getRatePlans()
                    .stream().filter(e -> rate.getHotelCode().equals(e.getHotelCode())
                            && String.valueOf(rate.getRatePlanId()).equals(e.getRatePlanId()))
                    .findFirst()
                    .orElse(null);
            if (Objects.isNull(ratePlan)) {
                return;
            }
            // 最小连住日期要 <= 1 且预期人数最少需要 <= 1
            if (ratePlan.getMinDays() > 1 || ratePlan.getMinAmount() > 1 || ratePlan.getMaxDays() < days
                    || ratePlan.getMinAdvHours() != 0 || ratePlan.getMaxAdvHours() / 24 < days) {
                return;
            }

            getBreakFastCount(htPriceCalendarElong, ratePlan.getMeals());
            getPriceCalendarPrice(htPriceCalendarElong, rate, htHotelStdInfo);
            getPriceCalendarCancel(htPriceCalendarElong, ratePlan.getPrepayRules(), ratePlan.getPrepayRuleExtends(), supplier.getSupplierType());
            htPriceCalendarElongService.saveOrUpdate(htPriceCalendarElong,
                    new LambdaQueryWrapper<HtPriceCalendarElong>()
                            .eq(HtPriceCalendarElong::getHotelId, htPriceCalendarElong.getHotelId())
                            .eq(HtPriceCalendarElong::getRoomTypeCode, htPriceCalendarElong.getRoomTypeCode())
                            .eq(HtPriceCalendarElong::getCalDate, htPriceCalendarElong.getCalDate())
                            .eq(HtPriceCalendarElong::getSourceId, htPriceCalendarElong.getSourceId())
                            .eq(HtPriceCalendarElong::getProductId, htPriceCalendarElong.getProductId()));
        });
    }

    @Override
    public void syncHotelOnlineRatesPush(String user, String appKey, String secretKey, String local) {
        try {
            List<HtHotelStdInfo> htHotelStdInfos = htHotelStdInfoService.list(
                    new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getSourceCode,
                            SourceEnum.ELONGDOM.getCode()).eq(HtHotelStdInfo::getSourceStatus, 1)
                            .eq(HtHotelStdInfo::getBookStatus, 1));
            if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isNotEmpty(htHotelStdInfos)
                    && redisUtil.lGetListSize(RedisKeyConstant.SYNC_MAIN_ELONG_HOTEL_ONLINE_RATE_PUSH) <= 0) {
                redisUtil.leftPushAll(RedisKeyConstant.SYNC_MAIN_ELONG_HOTEL_ONLINE_RATE_PUSH,
                        htHotelStdInfos.stream().map(HtHotelStdInfo::getHotelId).collect(Collectors.toList()));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void syncHotelOnlineRatesPop(String sourceId, String user, String appKey, String secretKey, String local, Integer days) {
        hotelRatesTaskPool.execute(
                () -> new SyncElongOnlineRatesThread(sourceId, user, appKey, secretKey, local, days));
    }

    @Override
    public void syncHotelOnlineRates(String sourceId, String user, String appKey, String secretKey, String local, String hotelIds, Date startDate, Date endDate, Integer days) {
        ElongHotelDetailReq req = ElongHotelDetailReq.builder()
                .hotelIds(hotelIds)
                .arrivalDate(startDate)
                .departureDate(endDate)
                .paymentType(ElongLocalEnum.PREPAY.getCode())
                .ratePlanId(0L)
                .options("1,2,4,12")
                .build();
        ElongHotelDetailResp elongHotelDetailResp = elongService.queryElongHotelDetail(user, appKey, secretKey, local, req);
        // 查找所有酒店
        elongHotelDetailResp.getHotels().forEach(hotel -> {
            init(sourceId, elongService.parseInnId(hotel.getHotelId()));
            // 查找当前酒店下的所有房型
            Optional.ofNullable(hotel.getRooms())
                    .orElse(new ArrayList<>())
                    .forEach(room -> {
                        // 查找当前房型下所有的rateplan
                        Optional.ofNullable(room.getRatePlans())
                                .orElse(new ArrayList<>())
                                .forEach(ratePlan -> {
                                    /**
                                     * guestType
                                     * 宾客类型的适用人群：
                                     * 0:不限
                                     * 1:其他
                                     * 2:持中国身份证的居民
                                     * 3:持回乡证的港澳人士
                                     * 4:持台胞证的台湾人士
                                     * 5:持中国护照的侨胞
                                     * 6:持大陆工作证/居留许可的外籍人士
                                     * 7:持非中国护照的外籍人士
                                     * 上述可能出现多个值，多个值时以英文逗号分隔，例：1,2,3
                                     */
                                    if (!(ratePlan.getGuestType().contains("0") || ratePlan.getGuestType().contains("2"))) {
                                        return;
                                    }
                                    // 最少预定数量 最少入住天数大于1 最少提前预定小时数大于0 是否可用
                                    if (ratePlan.getMinAmount() > 1 || ratePlan.getMinDays() > 1 || ratePlan.getMinAdvHours() > 0 ||
                                            ratePlan.getMaxAdvHours() / 24 < days || !ratePlan.getStatus()) {
                                        return;
                                    }
                                    /**
                                     * CurrentAlloment
                                     * 入住时间内不能超售的最小值。
                                     * 国内：当大于0小于5时，表示目前仅剩的房量，0表示房量充足，最少有1间可以预定，多间预定可能会失败；
                                     * 国际：999或9999表示房量充足，最少有1间可以预定，多间预定可能会失
                                     */
                                    int quota = ratePlan.getCurrentAlloment() > 0 && ratePlan.getCurrentAlloment() < 5 ? ratePlan.getCurrentAlloment() : 1;
                                    List<ElongHotelDetailResp.DayMeal> dayMealTable = ratePlan.getMeals().getDayMealTable();
                                    Optional.ofNullable(ratePlan.getNightlyRates())
                                            .orElse(new ArrayList<>())
                                            .forEach(nightlyRate -> {
                                                HtPriceCalendarElong htPriceCalendarElong = new HtPriceCalendarElong();
                                                htPriceCalendarElong.setSourceId(sourceId);
                                                htPriceCalendarElong.setHotelId(elongService.parseInnId(hotel.getHotelId()));
                                                htPriceCalendarElong.setRoomTypeCode(room.getRoomId());
                                                htPriceCalendarElong.setRoomTypeName(room.getName());
                                                htPriceCalendarElong.setProductId(
                                                        String.format("%s-%s-%s-%s", ratePlan.getRoomTypeId(), ratePlan.getRatePlanId(),
                                                                ratePlan.getHotelCode(), ratePlan.getInvoiceMode())
                                                );
                                                htPriceCalendarElong.setQuota(nightlyRate.getStatus() && nightlyRate.getMember().compareTo(new BigDecimal("-1")) != 0 ? quota : 0);
                                                htPriceCalendarElong.setPurchasePrice(nightlyRate.getMember());
                                                htPriceCalendarElong.setCurrencyCode(ratePlan.getCurrencyCode());
                                                htPriceCalendarElong.setBreakfastCount(getDayBreakfastCount(nightlyRate.getDate(), dayMealTable));
                                                calDayCancelRule(htPriceCalendarElong, ratePlan.getPrepayResult());
                                                htPriceCalendarElong.setCalDate(nightlyRate.getDate());
                                                htPriceCalendarElongService.saveOrUpdate(htPriceCalendarElong,
                                                        new LambdaQueryWrapper<HtPriceCalendarElong>()
                                                                .eq(HtPriceCalendarElong::getHotelId, htPriceCalendarElong.getHotelId())
                                                                .eq(HtPriceCalendarElong::getRoomTypeCode, htPriceCalendarElong.getRoomTypeCode())
                                                                .eq(HtPriceCalendarElong::getCalDate, htPriceCalendarElong.getCalDate())
                                                                .eq(HtPriceCalendarElong::getSourceId, htPriceCalendarElong.getSourceId())
                                                                .eq(HtPriceCalendarElong::getProductId, htPriceCalendarElong.getProductId()));
                                            });
                                });
                    });
        });
    }

    @Override
    public void syncRatesChosePush() {
        try {
            List<HtHotelStdInfo> htHotelStdInfos = htHotelStdInfoService.list(
                    new LambdaQueryWrapper<HtHotelStdInfo>().eq(HtHotelStdInfo::getSourceCode,
                            SourceEnum.ELONGDOM.getCode()).eq(HtHotelStdInfo::getSourceStatus, 1)
                            .eq(HtHotelStdInfo::getBookStatus, 1));
            if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isNotEmpty(htHotelStdInfos)
                    && redisUtil.lGetListSize(RedisKeyConstant.SYNC_MAIN_ELONG_CHOSE_RATE_PUSH) <= 0) {
                redisUtil.leftPushAll(RedisKeyConstant.SYNC_MAIN_ELONG_CHOSE_RATE_PUSH,
                        htHotelStdInfos.stream().map(HtHotelStdInfo::getHotelId).collect(Collectors.toList()));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void syncRatesChosePop(String sourceId) {
        hotelChoseRatesTaskPool.execute(
                () -> new SyncElongHotelChoseRatesThread(sourceId));
    }

    @Override
    public void syncRatesChose(String sourceId, String hotel) {
        OfflineFliggyRp param = new OfflineFliggyRp();
        param.setHotel(hotel);
        hworldCallBackClient.offLineRp(param);
        List<HtRoomTypeStdInfo> list = Optional.ofNullable(htRoomTypeStdInfoService.list(
                new LambdaQueryWrapper<HtRoomTypeStdInfo>()
                        .select(HtRoomTypeStdInfo::getRoomTypeCode)
                        .eq(HtRoomTypeStdInfo::getHotelId, hotel)))
                .orElse(new ArrayList<>());
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date today = DateUtil.parseDate(format.format(new Date()), "yyyy-MM-dd");
        Date yester = DateUtils.addDays(today, -1);
        // 删除当前酒店 前天的产品码
        htPriceCalendarElongProductService.remove(new LambdaQueryWrapper<HtPriceCalendarElongProduct>()
                .lt(HtPriceCalendarElongProduct::getCalDate, yester));
        list.forEach(htRoomTypeStdInfo -> {
            getProduct(hotel, htRoomTypeStdInfo.getRoomTypeCode(), sourceId, today, yester, "1");
            getProduct(hotel, htRoomTypeStdInfo.getRoomTypeCode(), sourceId, today, yester, "2");
            getProduct(hotel, htRoomTypeStdInfo.getRoomTypeCode(), sourceId, today, yester, "3");
            getProduct(hotel, htRoomTypeStdInfo.getRoomTypeCode(), sourceId, today, yester, "4");
        });
    }

    @Override
    public void syncIncrStatePop(String sourceId, String user, String appKey, String secretKey, String local, Integer shardingKey) {
        hotelChoseRatesTaskPool.execute(
                () -> new SyncElongIncrStateThread(sourceId, user, appKey, secretKey, local, shardingKey));
    }


    @Override
    public void syncIncrState(String sourceId, String user, String appKey, String secretKey, String local, Integer shardingKey) {
        // 获取增量的片
        Long lastId = elongService.getShardLastId(user, appKey, secretKey, local, ElongLocalEnum.STATE.getCode(), shardingKey);
        ElongQueryShardStateResp elongQueryShardStateResp =
                elongService.queryElongShardState(user, appKey, secretKey, local,
                        ElongQueryShardStateReq.builder()
                                .lastId(Objects.requireNonNull(lastId))
                                .shardingKey(shardingKey)
                                .count(5000)
                                .build());
        AtomicReference<Long> maxs = new AtomicReference<>();
        maxs.set(0L);
        Optional.ofNullable(elongQueryShardStateResp.getStates())
                .orElse(new ArrayList<>())
                .stream().sorted(Comparator.comparing(ElongQueryShardStateResp.States::getLastId))
                .forEach(states -> {
                    // 只处理下线
                    if (StringUtils.isNotBlank(states.getStateType())
                            && states.getStatus() != null && !states.getStatus()) {
                        switch (states.getStateType()) {
                            case "HotelId":
                                updateQuota(sourceId, states.getHotelId(), null, null);
                                break;
                            case "RoomId":
                                updateQuota(sourceId, states.getHotelId(), states.getRoomId(), null);
                                break;
                            case "RoomTypeId":
                            case "RatePlanId":
                            case "HotelCode":
                                updateQuota(sourceId, states.getHotelId(), states.getRoomId(),
                                        StringUtil.getNotBlank(states.getRoomTypeId(),
                                                StringUtil.valueOf(states.getRatePlanId()),
                                                StringUtil.valueOf(states.getRatePlanId())));
                                break;
                            default:
                                break;
                        }
                    }
                    maxs.set(states.getLastId());
                });
        String key = String.format("%s-%s-%s", RedisKeyConstant.SYNC_INCR_TYPE, ElongLocalEnum.STATE.getCode(), shardingKey);
        redisUtil.set(key, maxs.get());
    }


    private void calDayCancelRule(HtPriceCalendarElong htPriceCalendarElong, ElongHotelDetailResp.PrepayResult prepayResult) {
        if (Objects.isNull(prepayResult.getCancelType())) {
            htPriceCalendarElong.setCancelPolicyType(2);
        } else {
            // 同程 1：免费取消；2：收费取消，3：限时取消，4：不可取消
            // 中台 取消政策类型(1=免费退；2=不能退；3=阶梯退；4=限时免费退；5=限时部分退；6=限时扣首晚)
            switch (prepayResult.getCancelType()) {
                case 1:
                    htPriceCalendarElong.setCancelPolicyType(1);
                    break;
                case 2:
                    htPriceCalendarElong.setCancelPolicyType(2);
                    break;
                case 3:
                    htPriceCalendarElong.setCancelPolicyType(5);
                    break;
                case 4:
                    htPriceCalendarElong.setCancelPolicyType(2);
                    break;
                default:
                    break;
            }
        }

        htPriceCalendarElong.setCancelRule(JsonUtil.toString(prepayResult));
    }

    private Integer getDayBreakfastCount(Date date, List<ElongHotelDetailResp.DayMeal> dayMeals) {
        ElongHotelDetailResp.DayMeal dayMeal = dayMeals.stream()
                .filter(e ->
                        DateUtil.beginOfDay(e.getDate()).getTime() <= date.getTime()
                                && DateUtil.beginOfDay(e.getDate()).getTime() >= date.getTime())
                .findAny()
                .orElse(null);
        return Objects.nonNull(dayMeal) ? dayMeal.getBreakfastShare() : 0;
    }

    private void init(String sourceId, String hotelId) {
        htPriceCalendarElongService.update(null, new LambdaUpdateWrapper<HtPriceCalendarElong>()
                .set(HtPriceCalendarElong::getQuota, 0)
                .eq(HtPriceCalendarElong::getSourceId, sourceId)
                .eq(HtPriceCalendarElong::getHotelId, hotelId));
    }

    private void updateQuota(String sourceId, String hotelId, String roomId, String productId) {
        LambdaQueryWrapper<HtPriceCalendarElong> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(HtPriceCalendarElong::getSourceId, sourceId);
        wrapper.eq(StringUtils.isNotBlank(hotelId), HtPriceCalendarElong::getHotelId, hotelId);
        wrapper.eq(StringUtils.isNotBlank(roomId), HtPriceCalendarElong::getRoomTypeCode, roomId);
        Optional.ofNullable(htPriceCalendarElongService.list(wrapper))
                .orElse(new ArrayList<>()).forEach(htPriceCalendarElong -> {
            if (StringUtils.isNotBlank(productId)
                    && htPriceCalendarElong.getProductId().contains(productId)) {
                htPriceCalendarElongService.update(null, new LambdaUpdateWrapper<HtPriceCalendarElong>()
                        .set(HtPriceCalendarElong::getQuota, 0)
                        .eq(HtPriceCalendarElong::getId, htPriceCalendarElong.getId()));
            }
        });
    }

    /**
     * 找当前房型今日有库存且价格最低的码
     *
     * @param hotel
     * @param roomTypeCode
     * @param sourceId
     * @param today
     * @param yester
     * @param productCode
     */
    private void getProduct(String hotel, String roomTypeCode, String sourceId, Date today, Date yester, String productCode) {
        List<HtPriceCalendarElong> priceCalendarElongs = getPrice(hotel, roomTypeCode, sourceId, today, productCode);
        HtPriceCalendarElong htPriceCalendarElong = priceCalendarElongs.stream()
                .min(Comparator.comparing(HtPriceCalendarElong::getPurchasePrice))
                .orElse(new HtPriceCalendarElong());
        String productId = htPriceCalendarElong.getProductId();
        // 今日无产品码则查看昨日产品
        if (StringUtils.isBlank(productId)) {
            productId = Optional.ofNullable(htPriceCalendarElongProductService.getOne(
                    new LambdaQueryWrapper<HtPriceCalendarElongProduct>()
                            .eq(HtPriceCalendarElongProduct::getHotelId, hotel)
                            .eq(HtPriceCalendarElongProduct::getRoomTypeCode, roomTypeCode)
                            .eq(HtPriceCalendarElongProduct::getSourceId, sourceId)
                            .eq(HtPriceCalendarElongProduct::getProductCode, productCode)
                            .between(HtPriceCalendarElongProduct::getCalDate, yester, yester)))
                    .orElse(new HtPriceCalendarElongProduct()).getProductId();
        }
        if (StringUtils.isBlank(productId)) {
            getLastPrice(hotel, roomTypeCode, sourceId, today, productCode);
            htPriceCalendarElong = priceCalendarElongs.stream()
                    .min(Comparator.comparing(HtPriceCalendarElong::getPurchasePrice))
                    .orElse(new HtPriceCalendarElong());
            productId = htPriceCalendarElong.getProductId();
        }
        if (StringUtils.isBlank(productId)) {
            return;
        }
        saveProduct(hotel, roomTypeCode, sourceId, today, productCode, productId);
    }

    private List<HtPriceCalendarElong> getPrice(String hotel, String roomTypeCode, String sourceId, Date day, String productCode) {
        List<HtPriceCalendarElong> list = Optional.ofNullable(htPriceCalendarElongService.list(
                new LambdaQueryWrapper<HtPriceCalendarElong>()
                        .eq(HtPriceCalendarElong::getHotelId, hotel)
                        .eq(HtPriceCalendarElong::getRoomTypeCode, roomTypeCode)
                        .between(HtPriceCalendarElong::getCalDate, day, day)
                        .eq(HtPriceCalendarElong::getSourceId, sourceId)))
                .orElse(new ArrayList<>());
        List<HtPriceCalendarElong> targetList = new ArrayList<>();
        if (StringUtils.isNotBlank(productCode)) {
            switch (productCode) {
                // 飞猪侧是无早不可退 即中台有库存即可
                case "1":
                    targetList = list.stream()
                            .filter(e -> e.getQuota() > 0)
                            .collect(Collectors.toList());
                    break;
                // 飞猪侧是无早可退 即中台有库存、且取消规则是4即可
                case "2":
                    targetList = list.stream()
                            .filter(e -> e.getQuota() > 0 && 4 == e.getCancelPolicyType())
                            .collect(Collectors.toList());
                    break;
                // 飞猪侧是双早不可退 即中台有库存、且早参数为2即可
                case "3":
                    targetList = list.stream()
                            .filter(e -> e.getQuota() > 0 && 2 == e.getBreakfastCount())
                            .collect(Collectors.toList());
                    break;
                // 飞猪侧是双早可退 即中台有库存、取消规则是4且早参数为2即可
                case "4":
                    targetList = list.stream()
                            .filter(e -> e.getQuota() > 0 && 2 == e.getBreakfastCount() && 4 == e.getCancelPolicyType())
                            .collect(Collectors.toList());
                    break;
                default:
                    break;
            }
        }
        return targetList;
    }

    private List<HtPriceCalendarElong> getLastPrice(String hotel, String roomTypeCode, String sourceId, Date day, String productCode) {
        List<HtPriceCalendarElong> list = Optional.ofNullable(htPriceCalendarElongService.list(
                new LambdaQueryWrapper<HtPriceCalendarElong>()
                        .eq(HtPriceCalendarElong::getHotelId, hotel)
                        .eq(HtPriceCalendarElong::getRoomTypeCode, roomTypeCode)
                        .between(HtPriceCalendarElong::getCalDate, day, day)
                        .eq(HtPriceCalendarElong::getSourceId, sourceId)))
                .orElse(new ArrayList<>());
        List<HtPriceCalendarElong> targetList = new ArrayList<>();
        if (StringUtils.isNotBlank(productCode)) {
            switch (productCode) {
                // 飞猪侧是无早不可退 即中台有库存即可
                case "1":
                    targetList = list.stream()
                            .filter(e -> e.getQuota() >= 0)
                            .collect(Collectors.toList());
                    break;
                // 飞猪侧是无早可退 即中台有库存、且取消规则是4即可
                case "2":
                    targetList = list.stream()
                            .filter(e -> e.getQuota() >= 0 && 4 == e.getCancelPolicyType())
                            .collect(Collectors.toList());
                    break;
                // 飞猪侧是双早不可退 即中台有库存、且早参数为2即可
                case "3":
                    targetList = list.stream()
                            .filter(e -> e.getQuota() >= 0 && 2 == e.getBreakfastCount())
                            .collect(Collectors.toList());
                    break;
                // 飞猪侧是双早可退 即中台有库存、取消规则是4且早参数为2即可
                case "4":
                    targetList = list.stream()
                            .filter(e -> e.getQuota() >= 0 && 2 == e.getBreakfastCount() && 4 == e.getCancelPolicyType())
                            .collect(Collectors.toList());
                    break;
                default:
                    break;
            }
        }
        return targetList;
    }

    private void saveProduct(String hotel, String roomTypeCode, String sourceId, Date day, String productCode, String productId) {
        HtPriceCalendarElongProduct htPriceCalendarElongProduct = new HtPriceCalendarElongProduct();
        htPriceCalendarElongProduct.setHotelId(hotel);
        htPriceCalendarElongProduct.setRoomTypeCode(roomTypeCode);
        htPriceCalendarElongProduct.setSourceId(sourceId);
        htPriceCalendarElongProduct.setProductCode(productCode);
        htPriceCalendarElongProduct.setProductId(productId);
        htPriceCalendarElongProduct.setCalDate(day);
        htPriceCalendarElongProductService.save(htPriceCalendarElongProduct);
    }

    private void getPriceCalendarPrice(HtPriceCalendarElong htPriceCalendarElong, ELongQueryRateResp.Rate rate, HtHotelStdInfo htHotelStdInfo) {
        List<ElongQueryHotelResp.Supplier> suppliers = JsonUtil.toList(ElongQueryHotelResp.Supplier.class, htHotelStdInfo.getExtra());
        BigDecimal max = new BigDecimal("9999");
        if (CollectionUtils.isEmpty(suppliers)) {
            htPriceCalendarElong.setPurchasePrice(max);
            return;
        }
        ElongQueryHotelResp.Supplier supplier = suppliers.stream()
                .filter(e -> rate.getHotelCode().equals(e.getHotelCode()))
                .findFirst().orElse(null);
        if (Objects.isNull(supplier)) {
            htPriceCalendarElong.setPurchasePrice(max);
            return;
        }
        int day = DateUtil.dayForWeek(htPriceCalendarElong.getCalDate());
        int start = supplier.getWeekendStart();
        int end = supplier.getWeekendEnd();
        BigDecimal none = new BigDecimal("-1");
        if (0 == start && 0 == end) {
            htPriceCalendarElong.setPurchasePrice(rate.getMember().equals(none) ? max : rate.getMember());
        } else if (start < end) {
            htPriceCalendarElong.setPurchasePrice(day > start && day <= end ?
                    rate.getWeekend().equals(none) ? max : rate.getWeekend()
                    : rate.getMember().equals(none) ? max : rate.getMember());
        } else {
            htPriceCalendarElong.setPurchasePrice(day > end && day < start ?
                    rate.getMember().equals(none) ? max : rate.getMember()
                    : rate.getWeekend().equals(none) ? max : rate.getWeekend());
        }
    }

    private void getBreakFastCount(HtPriceCalendarElong htPriceCalendarElong, List<ElongQueryHotelRpResp.Meal> meals) {
        if (CollectionUtils.isEmpty(meals)) {
            htPriceCalendarElong.setBreakfastCount(0);
            return;
        }
        // 默认先找02的
        ElongQueryHotelRpResp.Meal meal = meals.stream().sorted(Comparator.comparing(ElongQueryHotelRpResp.Meal::getType).reversed())
                .filter(e -> e.getStartDate() != null && e.getEndDate() != null
                        && e.getStartDate().getTime() <= htPriceCalendarElong.getCalDate().getTime()
                        && e.getEndDate().getTime() >= htPriceCalendarElong.getCalDate().getTime())
                .findFirst().orElse(null);
        if (Objects.isNull(meal) || !meal.getIsInclude() || StringUtils.isBlank(meal.getWeekSet())
                || !meal.getWeekSet().contains(String.valueOf(DateUtil.dayForWeek(htPriceCalendarElong.getCalDate())))) {
            htPriceCalendarElong.setBreakfastCount(0);
            return;
        }
        htPriceCalendarElong.setBreakfastCount(meal.getNumberOfBreakfast());
    }

    /**
     * 取消政策类型(1=免费退；2=不能退；3=阶梯退；4=限时免费退；5=限时部分退；6=限时扣首晚)
     */
    private void getPriceCalendarCancel(HtPriceCalendarElong htPriceCalendarElong,
                                        List<ElongQueryHotelRpResp.PrepayRule> prepayRules,
                                        List<ElongQueryHotelRpResp.PrepayRuleExtend> prepayRuleExtends,
                                        String supplierType) {
        if (Objects.isNull(prepayRules) || CollectionUtils.isEmpty(prepayRules)) {
            return;
        }
        if (prepayRules.size() > 1) {
            htPriceCalendarElong.setCancelPolicyType(2);
            htPriceCalendarElong.setCancelRule(JsonUtil.toString(prepayRules));
            return;
        }
        Integer cancelType = null;
        if ("A".equals(supplierType)) {
            ElongQueryHotelRpResp.PrepayRule prepayRule = prepayRules.get(0);
            switch (prepayRule.getChangeRule()) {
                case "PrepayNoChange":
                    cancelType = 2;
                    break;
                case "PrepayNeedSomeDay":
                    cancelType = 5;
                    break;
                case "PrepayNeedOneTime":
                    cancelType = 4;
                default:
                    break;
            }
            htPriceCalendarElong.setCancelRule(JsonUtil.toString(prepayRules));
        } else if ("B".equals(supplierType)) {
            cancelType = 2;
            htPriceCalendarElong.setCancelRule(JsonUtil.toString(prepayRuleExtends));
        }
        htPriceCalendarElong.setCancelPolicyType(cancelType);
    }

    private Integer getAddBed(String addBed) {
        if (StringUtils.isNotBlank(addBed)) {
            switch (addBed) {
                case "可加床（免费）":
                case "可加床（收费）":
                case "可加床收费币种":
                case "可以加床，费用详询酒店":
                case "折叠床/加床":
                    return 1;
                case "不可加床":
                case "不可额外加床":
                    return 0;
                default:
                    break;
            }
        }
        return null;
    }

    private Integer getWindowType(String windowType) {
        if (StringUtils.isNotBlank(windowType)) {
            switch (windowType) {
                case "无窗":
                    return 0;
                case "部分窗":
                    return 2;
                case "内窗":
                    return 3;
                case "有窗":
                case "天窗":
                case "封闭窗":
                case "飘窗":
                    return 1;
                default:
                    break;
            }
        }
        return null;
    }

    private Integer getBedType(String bedType) {
        if (StringUtils.isNotBlank(bedType)) {
            switch (bedType) {
                case "大床":
                case "大床1.8米":
                case "大床1.5米":
                case "大床2米":
                    return 0;
                case "单人床":
                case "单人床1.2米":
                case "单人床1.1米":
                case "单人床1.3米":
                case "单人床1.35米":
                    return 1;
                case "双床":
                case "双床1.2米":
                case "双床1.1米":
                case "双床1.3米":
                case "双床1.35米":
                    return 2;
                case "特大床":
                    return 3;
                case "":
                    return 4;
                case "特殊床型":
                case "上下铺":
                case "榻榻米":
                case "三张床":
                case "圆床":
                case "水床":
                case "大/双床":
                case "沙发床":
                case "胶囊床":
                case "特长睡床（超过两米）":
                case "特长睡床（超过两米）(部分)":
                case "儿童床":
                case "多张床":
                case "帐篷床":
                case "开夜床(部分)":
                case "特长睡床(超过两米)":
                case "开夜床":
                case "高低床":
                case "小型双人床":
                    return 5;
                default:
                    break;
            }
        }
        return null;
    }

    private HtHotelStdInfo parseHotelInfo(ElongQueryHotelResp.Detail data) {
        HtHotelStdInfo info = new HtHotelStdInfo();
        info.setSourceCode(SourceEnum.ELONGDOM.getCode());
        info.setHotelId(elongService.parseInnId(data.getHotelId()));
        info.setHotelName(data.getHotelName());
        info.setHotelNameEn(data.getHotelNameEn());
        info.setBrandCode(data.getBrandId());
        info.setBrandName(data.getBrandName());
        info.setHotelAddress(data.getAddress());
        info.setHotelAddressEn(data.getAddressEn());
        info.setHotelTel(data.getPhone());
        info.setCityCode(data.getCityId());
        info.setCityName(data.getCityName());
        info.setIntroduction(data.getDescription());
        info.setSourceStatus(data.getHotelStatus() == 0 ? 1 : 0);
        info.setOverseas("3800".equals(data.getCountryId()) ? 0 : 1);
        info.setCountry(data.getCountryId());
        info.setLatitude(data.getGoogleLat());
        info.setLongitude(data.getGoogleLon());
        info.setMapPointType(info.getLatitude() != null ? 1 : null);
        if (info.getMapPointType() == null) {
            info.setLatitude(data.getBaiduLat());
            info.setLongitude(data.getBaiduLon());
            info.setMapPointType(info.getLatitude() != null ? 0 : null);
        }
        return info;
    }
}
